import pickle
import bisect

import pyglet
from pyglet.window import key
import cocos
from cocos.director import director
from cocos import tiles, actions, layer, sprite
import cocos.euclid as eu

from hud import HUD        
from map_consts import *
import gg

##game scene
##    scrolling manager
##        bg
##        tilemap
##        actors
##    hud
                                                                    
bindings = {
    'left': key.LEFT,
    'right': key.RIGHT
    }

##en test_tiles se hace
##    keyboard = key.KeyStateHandler()
##    director.window.push_handlers(keyboard)
##para acceder a kbd

class Player(sprite.Sprite):
    def __init__(self, world=None, position=(0,0), props=None):
        super(Player, self).__init__('player.png', position=position, anchor=(0,0))
        self.world = world
        self.max_x_speed = gg.player_vx
        self.x_accel = gg.player_ax
        self.x_speed = 0
        self.props = props

    def on_enter(self):
        super(Player, self).on_enter()
        self.scroller = self.get_ancestor(layer.ScrollingManager)
        self.keyboard = key.KeyStateHandler()
        director.window.push_handlers(self.keyboard)

    def on_exit(self):
        director.window.pop_handlers() #?
        super(Player, self).on_exit()

    def update(self, dt):
        x_thurst_cmd = (self.keyboard[bindings['right']] -
                        self.keyboard[bindings['left']])
        # simple test, things will be more complex
        x_acel = x_thurst_cmd * self.x_accel #* dt
        x_speed = self.x_speed + x_acel*dt
        if x_speed > self.max_x_speed:
            x_speed = self.max_x_speed
        elif -self.max_x_speed > x_speed:
            x_speed = -self.max_x_speed
        self.x_speed = x_speed
        self.x += x_speed*dt
        self.scroller.set_focus(self.x, self.y)
        
def poly_lenght(points_list):
    """calc polygonal lenght. las point automatically conects with the first"""
    a = complex(*points_list[-1])
    res = 0.0
    for p in points_list:
        b = complex(*p)
        res += abs(b-a) 
        a = b
    return res

class Patrol(cocos.cocosnode.CocosNode):
    def __init__(self, world=None, position=(0,0), props=None):
        super(Patrol, self).__init__()
        self.vel = float(float(props['vmul'])*gg.floater_v)
        self.world = world
        self.points = props['points']
        self.show = False

    def optimal_floaters_qtty(self):
        # TODO put real code and handle in terminal the swaping patrols
        # with different qttys
        return gg.floaters_per_patrol 

    def distribute(self, offset):
        n = self.optimal_floaters_qtty() 
        x0, y0 = offset
        points = [(x+x0, y+y0) for x,y in self.points]
        np = len(points)
        total_lenght = poly_lenght(points)
        round_time = total_lenght/self.vel
        sep_time = round_time/n
        dts = [ abs(complex(*points[(i+1)%np])-complex(*points[i]))/self.vel
                for i in xrange(np)]

        arrivals = [0.0]
        acc = 0.0  
        for i in xrange(np):
            acc += dts[i]
            arrivals.append(acc)

        t = 0.0
        j = 0 # con 1 falta un leg, con 0 el primer destino de la
              # de la seprimera plataforma es el primer pto, que es el pto
              # de partida. 
        t0 = arrivals[1] 
        pos = eu.Vector2(*points[0])
        res = []
#        print 'arrivals:', arrivals
        for i in xrange(n):
            action = actions.MoveTo(points[j], t0)
            for k in xrange(j,n):
                action += actions.MoveTo(points[(k+1)%np], arrivals[k+1]-arrivals[k])
            res.append((tuple(pos), action))
            
            i += 1
            t = i*sep_time
            j = bisect.bisect_left(arrivals, t, j)
            j -=1 
#            print 't,j:',t,j
            assert arrivals[j] <= t <= arrivals[j+1]
            d = (t-arrivals[j])/(arrivals[j+1]-arrivals[j])
            pos = d*eu.Vector2(*points[(j+1)%1]) + (1.0 - d)*eu.Vector2(*points[j])
            t0 = arrivals[j+1] - t

        self.full_action = res[0][1]
        return res        
                        

class Terminal(sprite.Sprite):
    def __init__(self, world=None, position=(0,0), props=None):
        super(Terminal, self).__init__('terminal.png', position=position, anchor=(0,0))
        self.color3 = gg.floater_colors3[int(props['color'])]
        sp = sprite.Sprite('terminal_mask.png', color=self.color3, position=(0,0), anchor=(0,0))
        self.add(sp)
        self.world = world
        self.id = props['id']
        self.initial_patrol_id = props['patrol']
        self.patrol = world.patrols_by_name[self.initial_patrol_id]
        self.floaters = []
        self.floaters_initialized = False
        
    def on_enter(self):
        super(Terminal, self).on_enter()
        if not self.floaters_initialized:
            self.floaters_initialized = True
            self.start_patrol()

    def start_patrol(self):
        ## fixme: convert a compilation time offsets in cell coords to pixel
        xx, yy = self.world.patrol_offsets[self.id][self.initial_patrol_id]
        res = self.patrol.distribute((xx*64, yy*64))
        for i in xrange(len(self.floaters),len(res)):
            actor = self.world.add_actor('floater', (0,0), props={'color':self.color3})
            self.floaters.append(actor)
        for i in xrange(len(res)):
            pos, action = res[i]
            self.floaters[i].position = pos
            self.floaters[i].do(action + actions.CallFuncS(self.next_patrol))

    def next_patrol(self, floater):
        floater.do(self.patrol.full_action + actions.CallFuncS(self.next_patrol))


class Floater(sprite.Sprite):
    def __init__(self, world=None, position=(0,0), props=None):
        super(Floater, self).__init__('floater.png', position=position, anchor=(0,0))
        self.color3 = props['color']
        sp = sprite.Sprite('floater_mask.png', color=self.color3, position=(0,0), anchor=(0,0))
        self.add(sp)

    def update(self, dt):
        pass
                                         
        

class_by_tile = {
    'player': Player,
    'terminal': Terminal,
    'patrol': Patrol,
    'floater': Floater
    }


class GameScene(cocos.scene.Scene):
    def __init__(self, level):
        super(GameScene, self).__init__()
        self.level = level

        self.patrols_by_name = {}
        self.static_actors = {} # (xx, yy): instance
        self.dynamic_actors = []
        self.load_level()
        print '\nending GameScene.__init__'

    def load_level(self):
        self.world_view = layer.ScrollingManager()
        self.add(self.world_view, z=1)
        basename = '%dmap'%self.level
        #basename = '2map'

        # tweaks
        fname = 'data/' + basename + '.tweaks.pkl'
        f=open(fname, 'rb')
        self.patrol_offsets = pickle.load(f)
        f.close()        
        
        # terrain view, a tilemap
        fname = basename + '.c.xml'
        full_map = tiles.load(fname)
        tiles_layer = full_map[basename + '.c']
        self.world_view.add(tiles_layer)
        self.actors_back = layer.ScrollableLayer()
        self.world_view.add(self.actors_back, z=2)
        self.player_layer = layer.ScrollableLayer()
        self.world_view.add(self.player_layer, z=3)
        self.platforms = layer.ScrollableLayer()
        self.world_view.add(self.platforms, z=4)

        # load compiled info
        f = open('data/' + basename+'.pkl', 'rb')
        collected = pickle.load(f)
        f.close()

        # set some model info
        self.tile_width = collected['tile_size'][0]
        self.tile_height = collected['tile_size'][1]
        self.touch = collected['touch']

        # instantiate patrols first, they will be needed later for 'terminal'
        patrols = collected['patrols']
        tw = self.tile_width
        th = self.tile_height
        for patrol_name in patrols:
            points, props = patrols[patrol_name]
            points = [ (x*tw, y*th) for x,y in points]
            props['points'] = points
            actor_type = 'patrol'
            actor = self.add_actor(actor_type, (0,0), props) #dummy position
            self.patrols_by_name[patrol_name] = actor

        # other actors except floaters, which are instantiated in Terminal
        actors_desc = collected['actors']
        for args in actors_desc:
            self.add_actor(*args)

        self.schedule(self.update)

    def add_actor(self, actor_type, cell_position, props):
        # signature deppends on compiler, currently
        # <tile name> <position in cell units> (<properties dict> | None)
        x = cell_position[0]*self.tile_width
        y = cell_position[1]*self.tile_height
        if tile_to_flags[actor_type] & STATIC_COLLIDABLE:
            if actor_type in class_by_tile:
                actor = class_by_tile[actor_type](world=self, position=(x,y), props=props)
            else:
                actor = sprite.Sprite(actor_type + '.png', anchor=(0,0))
                actor.position = (x,y)
                actor.actor_type = actor_type
                if props is not None:
                    # inject properties to sprite
                    for k in props:
                        setattr(actor, k, props[k])
            self.static_actors[cell_position] = actor
            self.actors_back.add(actor)
        else:
            # player, monsters, bullets. They will update position
            actor = class_by_tile[actor_type](world=self, position=(x,y), props=props)
            actor.actor_type = actor_type
            self.dynamic_actors.append(actor)
            if actor_type == 'player':
                self.player_layer.add(actor)
            elif actor_type == 'floater':
                self.platforms.add(actor)
            else:
                print 'actor_type:', actor_type
                raise NotImplemented
        return actor
                
            
    def update(self, dt):
        for actor in self.dynamic_actors:
            actor.update(dt)

        

    def next_level(self):
        pass
        
def get_scene(level):
    scene = GameScene(level)
    return scene


